/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

#include <mc/core/timer.h>
#include <mc/dbus/message.h>
#include <mc/log.h> // dli调试

#include "mctp.h"
#include "pcie_transport.h"

namespace dev {

mctp::~mctp() {
}

mctp::mctp(mc::engine::abstract_object* parent, uint16_t phy_addr, MCTP_MESSAGE_TYPE msg_type,
           std::string position)
    : mc::core::object(parent), m_phy_addr(phy_addr), m_msg_type(msg_type), m_position(position),
      m_parent(parent) {
    if (position != std::string("")) {
        m_mctp_name = m_mctp_name + position + "_" + std::to_string(phy_addr) + "/" +
                      std::to_string(msg_type);
    } else {
        m_mctp_name = m_mctp_name + std::to_string(phy_addr) + "/" + std::to_string(msg_type);
    }
    // m_mctp_name = std::string("0101010301_73/2");
    m_transport_path = std::string(PATH_MCTP_TRANSPORT) + "/" + m_mctp_name;
    m_endpoint_path  = std::string(PATH_MCTP_ENDPOINT) + "/" + m_mctp_name;
    elog("=================: ${m_transport_path}, ${m_endpoint_path}",
         ("m_transport_path", m_transport_path)("m_endpoint_path", m_endpoint_path));
}

bool mctp::request(mc::dict ctx, std::vector<uint8_t>& data, int32_t timeout_ms, std::string& rsp) {
    if (!m_create_endpoint_success) {
        elog("=================mctp::request create_endpoint failed, ${flag}",
             ("flag", m_create_endpoint_success));
        return false;
    }
    
    try {
        auto service = m_parent->get_service();
        if (service == nullptr) {
            elog("mctp::request service is nullptr");
            return false;
        }
        mc::variant result = service->timeout_call(
            mc::milliseconds(15000),
            "bmc.kepler.mctpd",
            m_endpoint_path,
            PCIE_ENDPOINT_INTERFACE,
            "Request",
            "a{ss}ayia{ss}a{ss}",
            mc::variants{mc::dict{}, data, timeout_ms, ctx, mc::dict{}}
        );
        rsp = result.as_string();
        return true;
        
    } catch (const std::exception& e) {
        return false;
    }
}

void mctp::create_transport_obj(mc::dict& object) {
    m_transport_obj = new transport_object(this);
    m_transport_obj->set_object_name(m_mctp_name);
    m_transport_obj->set_object_path(m_transport_path);
    m_transport_obj->set_position(m_position);
    m_transport_obj->init(object);
    auto service = m_parent->get_service();
    if (service == nullptr) {
        elog("mctp::create_transport_obj service is nullptr");
        return;
    }
    service->register_object(m_transport_obj);
}

void mctp::try_get_endpoint_object(uint8_t retry_count, std::function<void()> cb) {
    mc::dbus::message msg = mc::dbus::message::new_method_call(
        "bmc.kepler.mctpd", m_endpoint_path, "org.freedesktop.DBus.Properties", "GetAll");
    msg.set_sender("bmc.kepler.devmon");
    msg.writer() << std::string(PCIE_ENDPOINT_INTERFACE);
    auto service = m_parent->get_service();
    if (service == nullptr) {
        elog("mctp::try_get_endpoint_object service is nullptr");
        return ;
    }
    auto conn = service->get_connection();
    conn.async_send_with_reply(std::move(msg), mc::milliseconds(1000))
        .then([](mc::dbus::message reply) {
            if (reply.is_valid()) {
                auto     reader = reply.reader();
                mc::dict ep;
                reader >> ep;
                return ep;
            }
            return mc::dict();
        })
        .then([this, cb](mc::dict ep) {
            MC_ASSERT(!ep.empty(), "Get endpoint object(${name}) failed: endpoint is empty",
                      ("name", m_mctp_name));
            ilog("Get endpoint object(${name}) success", ("name", m_mctp_name));
            m_transport_obj->m_transport_interface.m_target_eid.set_value(
                ep["TargetEid"].as<uint8_t>());
            m_create_endpoint_success = true;
            cb();
        })
        .catch_error([this, retry_count, cb](auto err) {
            if (retry_count < 60) {
                elog("Get endpoint object(${name}) failed, retry...(${retry_count})",
                     ("name", m_mctp_name)("retry_count", retry_count));
                mc::core::timer::single_shot(mc::milliseconds(1000), this,
                                             [this, retry_count, cb]() {
                                                 try_get_endpoint_object(retry_count + 1, cb);
                                             });
            } else {
                elog("Get endpoint object(${name}) failed", ("name", m_mctp_name));
                cb();
            }
            return 0;
        });
}

void mctp::create_transport_and_endpoint(std::string module_name, std::function<void()> cb) {
    uint8_t  retry_count         = 0;
    mc::dict transport_interface = {
        {"ServiceName", module_name},
        {"TargetEid", 0},
        {"TargetPhyAddr", m_phy_addr},
        {"MessageType", m_msg_type},
    };
    mc::dict transport_object = {
        {PCIE_TRANSPORT_INTERFACE, transport_interface},
    };

    create_transport_obj(transport_object);

    try_get_endpoint_object(retry_count, cb);
    return;
}

} // namespace dev